/*
Copyright (C) 2013 Matthew Netsch

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
*/

#include "passmodel.h"
#include <QStringList>
#include <QDebug>
#include <QApplication>
#include <QClipboard>

PassModel::PassModel(PassMan::IDatabase *database, bool showPass, QObject *parent) :
    QStandardItemModel(parent),
    db(database),
    refresh(true),
    showPass(showPass)
{
    initState();
}

PassModel::~PassModel()
{
    delete db;
}

void
PassModel::enableUpdates()
{
    connect(this, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(updateModel(QStandardItem*)));
}

void
PassModel::disableUpdates()
{
    disconnect(this, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(updateModel(QStandardItem*)));
}

QString
PassModel::passify(
        QString text)
{
    if (showPass)
    {
        return text;
    }
    else
    {
        return text.fill('*');
    }
}

void
PassModel::initState()
{
    QStringList headers = QStringList();
    headers.append("Name");
    headers.append("Password");

    setHorizontalHeaderLabels(headers);

    //Register observer to database
    db->registerObserver(this);

    //Start updating model too
    enableUpdates();

    //Update
    notify();
}


void
PassModel::addPass(
        QString name,
        QString value)
{
    PassMan::IPass *pass = db->Create(name.toLatin1().constData());

    if (!pass)
    {
        return;
    }

    //Set the new value
    pass->setValue(value.toLatin1().constData());

    //Update database with pass
    db->Update(pass);

    //Clean up pass
    delete pass;
}

void
PassModel::updateModel(
        QStandardItem *item)
{
    PassMan::IPass *pass = db->Retrieve(item->row());

    if (!pass)
    {
        return;
    }

    //Set the new data
    if (item->column() == 0)
    {
        //Does name already exist?
        PassMan::IPass *old_pass = db->Retrieve(item->text().toLatin1().constData());

        //If already exists, then we need to revert to old data
        if (old_pass)
        {
            notify();

            delete old_pass;
            delete pass;
            return;
        }

        //Create pass with new name
        PassMan::IPass *new_pass = db->Create(item->text().toLatin1().constData());

        //Something went wrong, revert to old data and exit
        if (!new_pass)
        {
            notify();
            delete pass;
            return;
        }

        //Get value of old pass
        char *value = (char*)malloc(sizeof(char) * pass->getValueSize());
        pass->getValue(value, pass->getValueSize());
        new_pass->setValue(value);
        free(value);

        //Update new pass with old value
        db->Update(new_pass);

        //Delete pass with old name
        db->Destroy(pass);

        delete new_pass;
    }
    else
    {
        pass->setValue(item->text().toLatin1().constData());
        item->setText(passify(item->text()));

        //Disable refresh since we don't need to do it
        //and it'll cause odd behavior otherwise here
        refresh = false;
        db->Update(pass);
        refresh = true;
    }

    delete pass;
}

PassMan::IDatabase*
PassModel::clone()
{
    return db->clone();
}

bool
PassModel::isEqual(
        PassMan::IDatabase *db)
{
    if (!db)
    {
        return false;
    }

    if (db->getNumPass() != PassModel::db->getNumPass())
    {
        return false;
    }

    for (int i = 0; i < PassModel::db->getNumPass(); i++)
    {
        PassMan::IPass *pass = PassModel::db->Retrieve(i);

        if (!pass)
        {
            return false;
        }

        char *name  = (char*)malloc(sizeof(char) * pass->getNameSize());
        char *value = (char*)malloc(sizeof(char) * pass->getValueSize());

        pass->getName(name, pass->getNameSize());
        pass->getValue(value, pass->getValueSize());

        PassMan::IPass *other_pass = db->Retrieve(name);

        if (!other_pass)
        {
            free(name);
            free(value);
            delete(pass);
            return false;
        }

        char *other_value = (char*)malloc(sizeof(char) * other_pass->getValueSize());
        other_pass->getValue(other_value, other_pass->getValueSize());

        if (strcmp(value, other_value) != 0)
        {
            free(name);
            free(value);
            free(other_value);
            delete other_pass;
            delete pass;
            return false;
        }

        free(name);
        free(value);
        free(other_value);
        delete other_pass;
        delete pass;
    }

    return true;
}

void
PassModel::notify()
{
    if (!refresh)
    {
        emit modelChanged();
        return;
    }

    //Disable model update so that we don't do this recursively
    disableUpdates();  

    //Database was updated, need to refresh model cache here
    for (int i = 0; i < db->getNumPass(); i++)
    {
        PassMan::IPass *pass = db->Retrieve(i);
        char *name  = (char*)malloc(sizeof(char) * pass->getNameSize());
        char *value = (char*)malloc(sizeof(char) * pass->getValueSize());

        pass->getName(name, pass->getNameSize());
        pass->getValue(value, pass->getValueSize());

        //Don't replace items if we can help it
        if (i < rowCount())
        {
            item(i, 0)->setText(QString::fromLatin1(name));
            item(i, 1)->setText(passify(QString::fromLatin1(value)));
        }
        else
        {
            setItem(i, 0, new QStandardItem(QString::fromLatin1(name)));
            setItem(i, 1, new QStandardItem(passify(QString::fromLatin1(value))));
        }

        free(name);
        free(value);
        delete pass;
    }

    //Remove extra rows
    if ((rowCount() - db->getNumPass()) > 0)
    {
        removeRows(db->getNumPass(), rowCount() - db->getNumPass());
    }

    emit modelChanged();

    //Re-enable model update
    enableUpdates();
}

void
PassModel::deleteRow(int row)
{
    PassMan::IPass *pass = db->Retrieve(row);

    if (!pass)
    {
        return;
    }

    db->Destroy(pass);

    delete pass;
}

void
PassModel::changePass(
        int   row,
        char *value)
{
    PassMan::IPass *pass = db->Retrieve(row);

    if (!pass)
    {
        return;
    }

    pass->setValue(value);
    db->Update(pass);

    delete pass;
}

void
PassModel::copyPass(
        int row)
{
    PassMan::IPass *pass = db->Retrieve(row);

    if (!pass)
    {
        return;
    }

    char *value = (char*)malloc(sizeof(char) * pass->getValueSize());

    pass->getValue(value, pass->getValueSize());

    QApplication::clipboard()->setText(QString::fromLatin1(value));

    free(value);
    delete pass;
}

void PassModel::setShowPass(
        bool show)
{
    showPass = show;

    notify();
}
